/* * A Gradle plugin for the creation of Minecraft mods and MinecraftForge plugins. * Copyright (C) 2013 Minecraft Forge * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA */ package net.minecraftforge.gradle.user; import static net.minecraftforge.gradle.common.Constants.*; import static net.minecraftforge.gradle.user.UserConstants.*; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import com.beust.jcommander.internal.Lists; import org.gradle.api.Action; import org.gradle.api.DefaultTask; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.XmlProvider; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ExternalModuleDependency; import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.component.ComponentIdentifier; import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer; import org.gradle.api.artifacts.result.ArtifactResolutionResult; import org.gradle.api.artifacts.result.ArtifactResult; import org.gradle.api.artifacts.result.ComponentArtifactsResult; import org.gradle.api.artifacts.result.DependencyResult; import org.gradle.api.artifacts.result.ResolvedArtifactResult; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.FileCollection; import org.gradle.api.internal.plugins.DslObject; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.plugins.MavenPluginConvention; import org.gradle.api.specs.Spec; import org.gradle.api.tasks.GroovySourceSet; import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.ScalaSourceSet; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.compile.GroovyCompile; import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.javadoc.Javadoc; import org.gradle.api.tasks.scala.ScalaCompile; import org.gradle.jvm.JvmLibrary; import org.gradle.language.base.artifact.SourcesArtifact; import org.gradle.plugins.ide.eclipse.model.EclipseModel; import org.gradle.plugins.ide.idea.model.IdeaModel; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import groovy.lang.Closure; import net.minecraftforge.gradle.common.BasePlugin; import net.minecraftforge.gradle.tasks.ApplyFernFlowerTask; import net.minecraftforge.gradle.tasks.ApplyS2STask; import net.minecraftforge.gradle.tasks.CreateStartTask; import net.minecraftforge.gradle.tasks.DeobfuscateJar; import net.minecraftforge.gradle.tasks.ExtractS2SRangeTask; import net.minecraftforge.gradle.tasks.GenEclipseRunTask; import net.minecraftforge.gradle.tasks.PostDecompileTask; import net.minecraftforge.gradle.tasks.RemapSources; import net.minecraftforge.gradle.user.ReobfTaskFactory.ReobfTaskWrapper; import net.minecraftforge.gradle.util.GradleConfigurationException; import net.minecraftforge.gradle.util.delayed.DelayedFile; public abstract class UserBasePlugin<T extends UserBaseExtension> extends BasePlugin<T> { private boolean madeDecompTasks = false; // to gaurd against stupid programmers private final Closure<Object> makeRunDir = new Closure<Object>(null, null) { public Object call() { delayedFile(REPLACE_RUN_DIR).call().mkdirs(); return null; } }; @Override public final void applyPlugin() { // apply the plugins this.applyExternalPlugin("java"); this.applyExternalPlugin("eclipse"); this.applyExternalPlugin("idea"); // life cycle tasks Task task = makeTask(TASK_SETUP_CI, DefaultTask.class); task.setDescription("Sets up the bare minimum to build a minecraft mod. Ideally for CI servers"); task.setGroup("ForgeGradle"); task.dependsOn(TASK_DD_PROVIDED, TASK_DD_COMPILE); task = makeTask(TASK_SETUP_DEV, DefaultTask.class); task.setDescription("CIWorkspace + natives and assets to run and test Minecraft"); task.setGroup("ForgeGradle"); task.dependsOn(TASK_DD_PROVIDED, TASK_DD_COMPILE); task = makeTask(TASK_SETUP_DECOMP, DefaultTask.class); task.setDescription("DevWorkspace + the deobfuscated Minecraft source linked as a source jar."); task.setGroup("ForgeGradle"); task.dependsOn(TASK_DD_PROVIDED, TASK_DD_COMPILE); // create configs project.getConfigurations().maybeCreate(CONFIG_MC); project.getConfigurations().maybeCreate(CONFIG_PROVIDED); project.getConfigurations().maybeCreate(CONFIG_START); project.getConfigurations().maybeCreate(CONFIG_DEOBF_COMPILE); project.getConfigurations().maybeCreate(CONFIG_DEOBF_PROVIDED); project.getConfigurations().maybeCreate(CONFIG_DC_RESOLVED); project.getConfigurations().maybeCreate(CONFIG_DP_RESOLVED); // create the reobf named container NamedDomainObjectContainer<IReobfuscator> reobf = project.container(IReobfuscator.class, new ReobfTaskFactory(this)); project.getExtensions().add(EXT_REOBF, reobf); configureCompilation(); // Quality of life stuff for the users createSourceCopyTasks(); doDevTimeDeobf(); doDepAtExtraction(); configureRetromapping(); makeRunTasks(); // IDE stuff configureEclipse(); configureIntellij(); applyUserPlugin(); } @Override protected void afterEvaluate() { // to guard against stupid programmers if (!madeDecompTasks) { throw new RuntimeException("THE DECOMP TASKS HAVENT BEEN MADE!! STUPID FORGEGRADLE DEVELOPER!!!! :("); } // verify runDir is set if (Strings.isNullOrEmpty(getExtension().getRunDir())) { throw new GradleConfigurationException("RunDir is not set!"); } super.afterEvaluate(); // add replacements for run configs and gradle start T ext = getExtension(); replacer.putReplacement(REPLACE_CLIENT_TWEAKER, getClientTweaker(ext)); replacer.putReplacement(REPLACE_SERVER_TWEAKER, getServerTweaker(ext)); replacer.putReplacement(REPLACE_CLIENT_MAIN, getClientRunClass(ext)); replacer.putReplacement(REPLACE_SERVER_MAIN, getServerRunClass(ext)); // map configurations (only if the maven or maven publish plugins exist) mapConfigurations(); // configure source replacement. project.getTasks().withType(TaskSourceCopy.class, new Action<TaskSourceCopy>() { @Override public void execute(TaskSourceCopy t) { t.replace(getExtension().getReplacements()); t.include(getExtension().getIncludes()); } }); // add access transformers to deobf tasks addAtsToDeobf(); if (ext.getMakeObfSourceJar()) { project.getTasks().getByName("assemble").dependsOn(TASK_SRC_JAR); } // add task depends for reobf if (project.getPlugins().hasPlugin("maven")) { project.getTasks().getByName("uploadArchives").dependsOn(TASK_REOBF); if (ext.getMakeObfSourceJar()) { project.getTasks().getByName("uploadArchives").dependsOn(TASK_SRC_JAR); project.getArtifacts().add("archives", project.getTasks().getByName(TASK_SRC_JAR)); } } // add GradleStart dep { ConfigurableFileCollection col = project.files(getStartDir()); col.builtBy(TASK_MAKE_START); project.getDependencies().add(CONFIG_START, col); } // TODO: do some GradleStart stuff based on the MC version? // run task stuff // Add the mod and stuff to the classpath of the exec tasks. final Jar jarTask = (Jar) project.getTasks().getByName("jar"); if (this.hasClientRun()) { JavaExec exec = (JavaExec) project.getTasks().getByName("runClient"); exec.classpath(project.getConfigurations().getByName("runtime")); exec.classpath(project.getConfigurations().getByName(CONFIG_MC)); exec.classpath(project.getConfigurations().getByName(CONFIG_MC_DEPS)); exec.classpath(project.getConfigurations().getByName(CONFIG_START)); exec.classpath(jarTask.getArchivePath()); exec.dependsOn(jarTask); exec.jvmArgs(getClientJvmArgs(getExtension())); exec.args(getClientRunArgs(getExtension())); } if (this.hasServerRun()) { JavaExec exec = (JavaExec) project.getTasks().getByName("runServer"); exec.classpath(project.getConfigurations().getByName("runtime")); exec.classpath(project.getConfigurations().getByName(CONFIG_MC)); exec.classpath(project.getConfigurations().getByName(CONFIG_MC_DEPS)); exec.classpath(project.getConfigurations().getByName(CONFIG_START)); exec.classpath(jarTask.getArchivePath()); exec.dependsOn(jarTask); exec.jvmArgs(getServerJvmArgs(getExtension())); exec.jvmArgs(getServerRunArgs(getExtension())); } // complain about version number // blame cazzar if this regex doesnt work Pattern pattern = Pattern.compile("(?:(?:mc)?((?:\\d+)(?:.\\d+)+)-)?((?:0|[1-9][0-9]*)(?:\\.(?:0|[1-9][0-9]*))+)(?:-([\\da-z\\-]+(?:\\.[\\da-z\\-]+)*))?(?:\\+([\\da-z\\-]+(?:\\.[\\da-z\\-]+)*))?", Pattern.CASE_INSENSITIVE); if (!pattern.matcher(project.getVersion().toString()).matches()) { project.getLogger().warn("Version string '"+project.getVersion()+"' does not match SemVer specification "); project.getLogger().warn("You should try SemVer : http://semver.org/"); } } protected abstract void applyUserPlugin(); /** * Sets up the default settings for reobf tasks. * * @param reobf The task to setup */ protected void setupReobf(ReobfTaskWrapper reobf) { TaskSingleReobf task = reobf.getTask(); task.setExceptorCfg(delayedFile(EXC_SRG)); task.setFieldCsv(delayedFile(CSV_FIELD)); task.setMethodCsv(delayedFile(CSV_METHOD)); reobf.setMappingType(ReobfMappingType.NOTCH); JavaPluginConvention java = (JavaPluginConvention) project.getConvention().getPlugins().get("java"); reobf.setClasspath(java.getSourceSets().getByName("main").getCompileClasspath()); } @SuppressWarnings("unchecked") protected void makeDecompTasks(final String globalPattern, final String localPattern, Object inputJar, String inputTask, Object mcpPatchSet) { madeDecompTasks = true; // to gaurd against stupid programmers final DeobfuscateJar deobfBin = makeTask(TASK_DEOBF_BIN, DeobfuscateJar.class); { deobfBin.setSrg(delayedFile(SRG_NOTCH_TO_MCP)); deobfBin.setExceptorJson(delayedFile(MCP_DATA_EXC_JSON)); deobfBin.setExceptorCfg(delayedFile(EXC_MCP)); deobfBin.setFieldCsv(delayedFile(CSV_FIELD)); deobfBin.setMethodCsv(delayedFile(CSV_METHOD)); deobfBin.setApplyMarkers(false); deobfBin.setStripSynthetics(true); deobfBin.setInJar(inputJar); deobfBin.setOutJar(chooseDeobfOutput(globalPattern, localPattern, "Bin", "")); deobfBin.dependsOn(inputTask, TASK_GENERATE_SRGS, TASK_EXTRACT_DEP_ATS); } final Object deobfDecompJar = chooseDeobfOutput(globalPattern, localPattern, "", "srgBin"); final Object decompJar = chooseDeobfOutput(globalPattern, localPattern, "", "decomp"); final Object postDecompJar = chooseDeobfOutput(globalPattern, localPattern, "", "decompFixed"); final Object remapped = chooseDeobfOutput(globalPattern, localPattern, "Src", "sources"); final Object recompiledJar = chooseDeobfOutput(globalPattern, localPattern, "Src", ""); final DeobfuscateJar deobfDecomp = makeTask(TASK_DEOBF, DeobfuscateJar.class); { deobfDecomp.setSrg(delayedFile(SRG_NOTCH_TO_SRG)); deobfDecomp.setExceptorJson(delayedFile(MCP_DATA_EXC_JSON)); deobfDecomp.setExceptorCfg(delayedFile(EXC_SRG)); deobfDecomp.setApplyMarkers(true); deobfDecomp.setInJar(inputJar); deobfDecomp.setOutJar(deobfDecompJar); deobfDecomp.dependsOn(inputTask, TASK_GENERATE_SRGS, TASK_EXTRACT_DEP_ATS); // todo grab correct task to depend on } final ApplyFernFlowerTask decompile = makeTask(TASK_DECOMPILE, ApplyFernFlowerTask.class); { decompile.setInJar(deobfDecompJar); decompile.setOutJar(decompJar); decompile.setFernflower(delayedFile(JAR_FERNFLOWER)); decompile.dependsOn(TASK_DL_FERNFLOWER, deobfDecomp); } final PostDecompileTask postDecomp = makeTask(TASK_POST_DECOMP, PostDecompileTask.class); { postDecomp.setInJar(decompJar); postDecomp.setOutJar(postDecompJar); postDecomp.setPatches(mcpPatchSet); postDecomp.setAstyleConfig(delayedFile(MCP_DATA_STYLE)); postDecomp.dependsOn(decompile); } final RemapSources remap = makeTask(TASK_REMAP, RemapSources.class); { remap.setInJar(postDecompJar); remap.setOutJar(remapped); remap.setFieldsCsv(delayedFile(CSV_FIELD)); remap.setMethodsCsv(delayedFile(CSV_METHOD)); remap.setParamsCsv(delayedFile(CSV_PARAM)); remap.dependsOn(postDecomp); } final TaskRecompileMc recompile = makeTask(TASK_RECOMPILE, TaskRecompileMc.class); { recompile.setInSources(remapped); recompile.setClasspath(CONFIG_MC_DEPS); recompile.setOutJar(recompiledJar); recompile.dependsOn(remap, TASK_DL_VERSION_JSON); } // create GradleStart final CreateStartTask makeStart = makeTask(TASK_MAKE_START, CreateStartTask.class); { makeStart.addResource(GRADLE_START_RESOURCES[2]); // gradle start common. if (this.hasClientRun()) { makeStart.addResource(GRADLE_START_RESOURCES[0]); // gradle start makeStart.addReplacement("@@ASSETINDEX@@", delayedString(REPLACE_ASSET_INDEX)); makeStart.addReplacement("@@ASSETSDIR@@", delayedFile(REPLACE_CACHE_DIR + "/assets")); makeStart.addReplacement("@@NATIVESDIR@@", delayedFile(DIR_NATIVES)); makeStart.addReplacement("@@TWEAKERCLIENT@@", delayedString(REPLACE_CLIENT_TWEAKER)); makeStart.addReplacement("@@BOUNCERCLIENT@@", delayedString(REPLACE_CLIENT_MAIN)); makeStart.dependsOn(TASK_DL_ASSET_INDEX, TASK_DL_ASSETS, TASK_EXTRACT_NATIVES); } if (this.hasServerRun()) { makeStart.addResource(GRADLE_START_RESOURCES[1]); // gradle start makeStart.addReplacement("@@TWEAKERSERVER@@", delayedString(REPLACE_SERVER_TWEAKER)); makeStart.addReplacement("@@BOUNCERSERVER@@", delayedString(REPLACE_SERVER_MAIN)); } makeStart.addReplacement("@@MCVERSION@@", delayedString(REPLACE_MC_VERSION)); makeStart.addReplacement("@@SRGDIR@@", delayedFile(DIR_MCP_MAPPINGS + "/srgs/")); makeStart.addReplacement("@@SRG_NOTCH_SRG@@", delayedFile(SRG_NOTCH_TO_SRG)); makeStart.addReplacement("@@SRG_NOTCH_MCP@@", delayedFile(SRG_NOTCH_TO_MCP)); makeStart.addReplacement("@@SRG_SRG_MCP@@", delayedFile(SRG_SRG_TO_MCP)); makeStart.addReplacement("@@SRG_MCP_SRG@@", delayedFile(SRG_MCP_TO_SRG)); makeStart.addReplacement("@@SRG_MCP_NOTCH@@", delayedFile(SRG_MCP_TO_NOTCH)); makeStart.addReplacement("@@CSVDIR@@", delayedFile(DIR_MCP_MAPPINGS)); makeStart.setStartOut(getStartDir()); makeStart.addClasspathConfig(CONFIG_MC_DEPS); makeStart.mustRunAfter(deobfBin, recompile); } // setup reobf... ((NamedDomainObjectContainer<IReobfuscator>) project.getExtensions().getByName(EXT_REOBF)).create("jar"); // add setup dependencies project.getTasks().getByName(TASK_SETUP_CI).dependsOn(deobfBin); project.getTasks().getByName(TASK_SETUP_DEV).dependsOn(deobfBin, makeStart); project.getTasks().getByName(TASK_SETUP_DECOMP).dependsOn(recompile, makeStart); // configure MC compiling. This AfterEvaluate section should happen after the one made in // also configure the dummy task dependencies project.afterEvaluate(new Action<Project>() { @Override public void execute(Project project) { if (project.getState().getFailure() != null) return; // the recompiled jar exists, or the decomp task is part of the build boolean isDecomp = project.file(recompiledJar).exists() || project.getGradle().getStartParameter().getTaskNames().contains(TASK_SETUP_DECOMP); // set task dependencies if (!isDecomp) { project.getTasks().getByName("compileJava").dependsOn(UserConstants.TASK_DEOBF_BIN); project.getTasks().getByName("compileApiJava").dependsOn(UserConstants.TASK_DEOBF_BIN); } afterDecomp(isDecomp, useLocalCache(getExtension()), CONFIG_MC); } }); } /** * This method returns an object that resolved to the correct pattern based on the useLocalCache() method * * @param globalPattern The global pattern * @param localPattern The local pattern * @param appendage The appendage * @param classifier The classifier * * @return useable deobfsucated output file */ @SuppressWarnings("serial") protected final Object chooseDeobfOutput(final String globalPattern, final String localPattern, final String appendage, final String classifier) { return new Closure<DelayedFile>(project, this) { public DelayedFile call() { String classAdd = Strings.isNullOrEmpty(classifier) ? "" : "-" + classifier; String str = useLocalCache(getExtension()) ? localPattern : globalPattern; return delayedFile(String.format(str, appendage) + classAdd + ".jar"); } }; } /** * A boolean used to cache the output of useLocalCache; */ protected boolean useLocalCache = false; /** * This method is called sufficiently late. Either afterEvaluate or inside a task, thus it has the extension object. * This method is called to decide whether or not to use the project-local cache instead of the global cache. * The actual locations of each cache are specified elsewhere. * TODO: add see annotations * @param extension The extension object of this plugin * @return whether or not to use the local cache */ protected boolean useLocalCache(T extension) { if (useLocalCache) return true; // checks to see if any access transformers were added. useLocalCache = !extension.getAccessTransformers().isEmpty() || extension.isUseDepAts(); return useLocalCache; } /** * Creates the api SourceSet and configures the classpaths of all the SourceSets to have MC and the MC deps in them. * Also sets the target JDK to java 6 */ protected void configureCompilation() { // get convention JavaPluginConvention javaConv = (JavaPluginConvention) project.getConvention().getPlugins().get("java"); SourceSet main = javaConv.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); SourceSet test = javaConv.getSourceSets().getByName(SourceSet.TEST_SOURCE_SET_NAME); SourceSet api = javaConv.getSourceSets().create("api"); api.setCompileClasspath(api.getCompileClasspath() .plus(project.getConfigurations().getByName(CONFIG_MC)) .plus(project.getConfigurations().getByName(CONFIG_MC_DEPS)) .plus(project.getConfigurations().getByName(CONFIG_PROVIDED))); main.setCompileClasspath(main.getCompileClasspath() .plus(api.getOutput()) .plus(project.getConfigurations().getByName(CONFIG_MC)) .plus(project.getConfigurations().getByName(CONFIG_MC_DEPS)) .plus(project.getConfigurations().getByName(CONFIG_PROVIDED))); main.setRuntimeClasspath(main.getCompileClasspath() .plus(api.getOutput()) .plus(project.getConfigurations().getByName(CONFIG_MC)) .plus(project.getConfigurations().getByName(CONFIG_MC_DEPS)) .plus(project.getConfigurations().getByName(CONFIG_START))); test.setCompileClasspath(test.getCompileClasspath() .plus(api.getOutput()) .plus(project.getConfigurations().getByName(CONFIG_MC)) .plus(project.getConfigurations().getByName(CONFIG_MC_DEPS)) .plus(project.getConfigurations().getByName(CONFIG_PROVIDED))); test.setRuntimeClasspath(test.getRuntimeClasspath() .plus(api.getOutput()) .plus(project.getConfigurations().getByName(CONFIG_MC)) .plus(project.getConfigurations().getByName(CONFIG_MC_DEPS))); project.getConfigurations().getByName(JavaPlugin.COMPILE_CONFIGURATION_NAME).extendsFrom(project.getConfigurations().getByName(CONFIG_DC_RESOLVED)); project.getConfigurations().getByName(CONFIG_PROVIDED).extendsFrom(project.getConfigurations().getByName(CONFIG_DP_RESOLVED)); project.getConfigurations().getByName(api.getCompileConfigurationName()).extendsFrom(project.getConfigurations().getByName("compile")); project.getConfigurations().getByName(JavaPlugin.TEST_COMPILE_CONFIGURATION_NAME).extendsFrom(project.getConfigurations().getByName("apiCompile")); Javadoc javadoc = (Javadoc) project.getTasks().getByName(JavaPlugin.JAVADOC_TASK_NAME); javadoc.setClasspath(main.getOutput().plus(main.getCompileClasspath())); // libs folder dependencies project.getDependencies().add(JavaPlugin.COMPILE_CONFIGURATION_NAME, project.fileTree("libs")); // set the compile target javaConv.setSourceCompatibility("1.6"); javaConv.setTargetCompatibility("1.6"); } /** * Creates and partially configures the source replacement tasks. The actual replacements must be configured afterEvaluate. */ protected void createSourceCopyTasks() { JavaPluginConvention javaConv = (JavaPluginConvention) project.getConvention().getPlugins().get("java"); Action<SourceSet> action = new Action<SourceSet>() { @Override public void execute(SourceSet set) { TaskSourceCopy task; String capName = set.getName().substring(0, 1).toUpperCase() + set.getName().substring(1); String taskPrefix = "source"+capName; File dirRoot = new File(project.getBuildDir(), "sources/"+set.getName()); // java { File dir = new File(dirRoot, "java"); task = makeTask(taskPrefix+"Java", TaskSourceCopy.class); task.setSource(set.getJava()); task.setOutput(dir); // must get replacements from extension afterEvaluate() JavaCompile compile = (JavaCompile) project.getTasks().getByName(set.getCompileJavaTaskName()); compile.dependsOn(task); compile.setSource(dir); } // scala if (project.getPlugins().hasPlugin("scala")) { ScalaSourceSet langSet = (ScalaSourceSet) new DslObject(set).getConvention().getPlugins().get("scala"); File dir = new File(dirRoot, "scala"); task = makeTask(taskPrefix+"Scala", TaskSourceCopy.class); task.setSource(langSet.getScala()); task.setOutput(dir); // must get replacements from extension afterEValuate() ScalaCompile compile = (ScalaCompile) project.getTasks().getByName(set.getCompileTaskName("scala")); compile.dependsOn(task); compile.setSource(dir); } // groovy if (project.getPlugins().hasPlugin("groovy")) { GroovySourceSet langSet = (GroovySourceSet) new DslObject(set).getConvention().getPlugins().get("groovy"); File dir = new File(dirRoot, "groovy"); task = makeTask(taskPrefix+"Groovy", TaskSourceCopy.class); task.setSource(langSet.getGroovy()); task.setOutput(dir); // must get replacements from extension afterEValuate() GroovyCompile compile = (GroovyCompile) project.getTasks().getByName(set.getCompileTaskName("groovy")); compile.dependsOn(task); compile.setSource(dir); } } }; // for existing sourceSets for (SourceSet set : javaConv.getSourceSets()) { action.execute(set); } // for user-defined ones javaConv.getSourceSets().whenObjectAdded(action); } protected final void doDevTimeDeobf() { final Task compileDummy = getDummyDep("compile", delayedFile(DIR_DEOBF_DEPS + "/compileDummy.jar"), TASK_DD_COMPILE); final Task providedDummy = getDummyDep("compile", delayedFile(DIR_DEOBF_DEPS + "/providedDummy.jar"), TASK_DD_PROVIDED); setupDevTimeDeobf(compileDummy, providedDummy); } protected void setupDevTimeDeobf(final Task compileDummy, final Task providedDummy) { // die wih error if I find invalid types... project.afterEvaluate(new Action<Project>() { @Override public void execute(Project project) { if (project.getState().getFailure() != null) return; // add maven repo addMavenRepo(project, "deobfDeps", delayedFile(DIR_DEOBF_DEPS).call().getAbsoluteFile().toURI().getPath()); remapDeps(project, project.getConfigurations().getByName(CONFIG_DEOBF_COMPILE), CONFIG_DC_RESOLVED, compileDummy); remapDeps(project, project.getConfigurations().getByName(CONFIG_DEOBF_PROVIDED), CONFIG_DP_RESOLVED, providedDummy); } }); } @SuppressWarnings("unchecked") protected void remapDeps(Project project, Configuration config, String resolvedConfig, Task dummyTask) { // only allow maven/ivy dependencies for (Dependency dep : config.getIncoming().getDependencies()) { if (!(dep instanceof ExternalModuleDependency)) { throw new GradleConfigurationException("Only allowed to use maven dependencies for this. If its a jar file, deobfuscate it yourself."); } } int taskId = 0; // FOR SOURCES! HashMap<ComponentIdentifier, ModuleVersionIdentifier> idMap = Maps.newHashMap(); // FOR BINARIES for (ResolvedArtifact artifact : config.getResolvedConfiguration().getResolvedArtifacts()) { ModuleVersionIdentifier module = artifact.getModuleVersion().getId(); String group = "deobf." + module.getGroup(); // Add artifacts that will be remapped to get their sources idMap.put(artifact.getId().getComponentIdentifier(), module); TaskSingleDeobfBin deobf = makeTask(config.getName() + "DeobfDepTask" + (taskId++), TaskSingleDeobfBin.class); deobf.setInJar(artifact.getFile()); deobf.setOutJar(getFile(DIR_DEOBF_DEPS, group, module.getName(), module.getVersion(), null)); deobf.setFieldCsv(delayedFile(CSV_FIELD)); deobf.setMethodCsv(delayedFile(CSV_METHOD)); deobf.dependsOn(TASK_EXTRACT_MAPPINGS); dummyTask.dependsOn(deobf); project.getDependencies().add(resolvedConfig, group + ":" + module.getName() + ":" + module.getVersion()); } for (DependencyResult depResult : config.getIncoming().getResolutionResult().getAllDependencies()) { idMap.put(depResult.getFrom().getId(), depResult.getFrom().getModuleVersion()); } ArtifactResolutionResult result = project.getDependencies().createArtifactResolutionQuery() .forComponents(idMap.keySet()) .withArtifacts(JvmLibrary.class, SourcesArtifact.class) .execute(); for (ComponentArtifactsResult comp : result.getResolvedComponents()) { ModuleVersionIdentifier module = idMap.get(comp.getId()); String group = "deobf." + module.getGroup(); for (ArtifactResult art : comp.getArtifacts(SourcesArtifact.class)) { // there can only be One! RemapSources remap = makeTask(config.getName() + "RemapDepSourcesTask" + (taskId++), RemapSources.class); remap.setInJar(((ResolvedArtifactResult) art).getFile()); remap.setOutJar(getFile(DIR_DEOBF_DEPS, group, module.getName(), module.getVersion(), "sources")); remap.setFieldsCsv(delayedFile(CSV_FIELD)); remap.setMethodsCsv(delayedFile(CSV_METHOD)); remap.setParamsCsv(delayedFile(CSV_PARAM)); remap.dependsOn(TASK_EXTRACT_MAPPINGS); dummyTask.dependsOn(remap); break; } } } private Object getFile(String baseDir, String group, String name, String version, String classifier) { return delayedFile( baseDir + "/" + group.replace('.', '/') + "/" + name + "/" + version + "/" + name + "-" + version + (Strings.isNullOrEmpty(classifier) ? "" : "-" + classifier) + ".jar"); } protected void doDepAtExtraction() { TaskExtractDepAts extract = makeTask(TASK_EXTRACT_DEP_ATS, TaskExtractDepAts.class); extract.addCollection("compile"); extract.addCollection(CONFIG_PROVIDED); extract.addCollection(CONFIG_DEOBF_COMPILE); extract.addCollection(CONFIG_DEOBF_PROVIDED); extract.setOutputDir(delayedFile(DIR_DEP_ATS)); extract.onlyIf(new Spec<Object>() { @Override public boolean isSatisfiedBy(Object arg0) { return getExtension().isUseDepAts(); } }); extract.doLast(new Action<Task>() { @Override public void execute(Task task) { DeobfuscateJar binDeobf = (DeobfuscateJar) task.getProject().getTasks().getByName(TASK_DEOBF_BIN); DeobfuscateJar decompDeobf = (DeobfuscateJar) task.getProject().getTasks().getByName(TASK_DEOBF); for (File file : task.getProject().fileTree(delayedFile(DIR_DEP_ATS))) { binDeobf.addAt(file); decompDeobf.addAt(file); } } }); getExtension().atSource(delayedFile(DIR_DEP_ATS)); } protected void configureRetromapping() { JavaPluginConvention javaConv = (JavaPluginConvention) project.getConvention().getPlugins().get("java"); Action<SourceSet> retromapCreator = new Action<SourceSet>() { @Override public void execute(final SourceSet set) { // native non-replaced DelayedFile rangeMap = delayedFile(getSourceSetFormatted(set, TMPL_RANGEMAP)); DelayedFile retroMapped = delayedFile(getSourceSetFormatted(set, TMPL_RETROMAPED)); final ExtractS2SRangeTask extractRangemap = makeTask(getSourceSetFormatted(set, TMPL_TASK_RANGEMAP), ExtractS2SRangeTask.class); extractRangemap.addSource(new File(project.getBuildDir(), "sources/main/java")); extractRangemap.setRangeMap(rangeMap); project.afterEvaluate(new Action<Project>() { @Override public void execute(Project project) { extractRangemap.addLibs(set.getCompileClasspath()); } }); ApplyS2STask retromap = makeTask(getSourceSetFormatted(set, TMPL_TASK_RETROMAP), ApplyS2STask.class); retromap.addSource(set.getAllJava()); retromap.setOut(retroMapped); retromap.addSrg(delayedFile(SRG_MCP_TO_SRG)); retromap.addExc(delayedFile(EXC_MCP)); retromap.addExc(delayedFile(EXC_SRG)); retromap.setRangeMap(rangeMap); retromap.dependsOn(TASK_GENERATE_SRGS, extractRangemap); // TODO: add replacing extract task // for replaced sources rangeMap = delayedFile(getSourceSetFormatted(set, TMPL_RANGEMAP_RPL)); retroMapped = delayedFile(getSourceSetFormatted(set, TMPL_RETROMAPED_RPL)); File replacedSource = new File(project.getBuildDir(), "sources/"+set.getName()+"/java"); final ExtractS2SRangeTask extractRangemap2 = makeTask(getSourceSetFormatted(set, TMPL_TASK_RANGEMAP_RPL), ExtractS2SRangeTask.class); extractRangemap2.addSource(replacedSource); extractRangemap2.setRangeMap(rangeMap); project.afterEvaluate(new Action<Project>() { @Override public void execute(Project project) { extractRangemap2.addLibs(set.getCompileClasspath()); } }); extractRangemap2.dependsOn(getSourceSetFormatted(set, "source%sJava")); retromap = makeTask(getSourceSetFormatted(set, TMPL_TASK_RETROMAP_RPL), ApplyS2STask.class); retromap.addSource(replacedSource); retromap.setOut(retroMapped); retromap.addSrg(delayedFile(SRG_MCP_TO_SRG)); retromap.addExc(delayedFile(EXC_MCP)); retromap.addExc(delayedFile(EXC_SRG)); retromap.setRangeMap(rangeMap); retromap.dependsOn(TASK_GENERATE_SRGS, extractRangemap2); } }; // for existing sourceSets for (SourceSet set : javaConv.getSourceSets()) { retromapCreator.execute(set); } // for user-defined ones javaConv.getSourceSets().whenObjectAdded(retromapCreator); final SourceSet main = javaConv.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); // make retromapped sourcejar final Jar sourceJar = makeTask(TASK_SRC_JAR, Jar.class); final String retromappedSrc = getSourceSetFormatted(main, TMPL_RETROMAPED_RPL); sourceJar.from(main.getOutput().getResourcesDir()); sourceJar.setClassifier("sources"); sourceJar.dependsOn(main.getCompileJavaTaskName(), main.getProcessResourcesTaskName(), getSourceSetFormatted(main, TMPL_TASK_RETROMAP_RPL)); sourceJar.from(new Closure<Object>(this, this) { public Object call() { File file = delayedFile(retromappedSrc).call(); if (file.exists()) return sourceJar.getProject().zipTree(delayedFile(retromappedSrc)); else return new ArrayList<File>(); } }); // get scala sources too project.afterEvaluate(new Action<Project>() { @Override public void execute(Project project) { if (project.getPlugins().hasPlugin("scala")) { ScalaSourceSet langSet = (ScalaSourceSet) new DslObject(main).getConvention().getPlugins().get("scala"); sourceJar.from(langSet.getAllScala()); } } }); } protected void makeRunTasks() { if (this.hasClientRun()) { JavaExec exec = makeTask("runClient", JavaExec.class); exec.getOutputs().dir(delayedFile(REPLACE_RUN_DIR)); exec.setMain(GRADLE_START_CLIENT); exec.workingDir(delayedFile(REPLACE_RUN_DIR)); exec.setStandardOutput(System.out); exec.setErrorOutput(System.err); exec.setGroup("ForgeGradle"); exec.setDescription("Runs the Minecraft client"); exec.doFirst(makeRunDir); exec.dependsOn("makeStart"); } if (this.hasServerRun()) { JavaExec exec = makeTask("runServer", JavaExec.class); exec.getOutputs().dir(delayedFile(REPLACE_RUN_DIR)); exec.setMain(GRADLE_START_SERVER); exec.workingDir(delayedFile(REPLACE_RUN_DIR)); exec.setStandardOutput(System.out); exec.setStandardInput(System.in); exec.setErrorOutput(System.err); exec.setGroup("ForgeGradle"); exec.setDescription("Runs the Minecraft Server"); exec.doFirst(makeRunDir); exec.dependsOn("makeStart"); } } protected final TaskDepDummy getDummyDep(String config, DelayedFile dummy, String taskName) { TaskDepDummy dummyTask = makeTask(taskName, TaskDepDummy.class); dummyTask.setOutputFile(dummy); ConfigurableFileCollection col = project.files(dummy); col.builtBy(dummyTask); project.getDependencies().add(config, col); return dummyTask; } protected void mapConfigurations() { if (project.getPlugins().hasPlugin("maven")) { MavenPluginConvention mavenConv = (MavenPluginConvention) project.getConvention().getPlugins().get("maven"); Conf2ScopeMappingContainer mappings = mavenConv.getConf2ScopeMappings(); ConfigurationContainer configs = project.getConfigurations(); final int priority = 500; // 500 is more than the compile config which is at 300 mappings.setSkipUnmappedConfs(true); // dont want unmapped confs bieng compile deps.. mappings.addMapping(priority, configs.getByName(CONFIG_PROVIDED), Conf2ScopeMappingContainer.PROVIDED); mappings.addMapping(priority, configs.getByName(CONFIG_DEOBF_COMPILE), Conf2ScopeMappingContainer.COMPILE); mappings.addMapping(priority, configs.getByName(CONFIG_DEOBF_PROVIDED), Conf2ScopeMappingContainer.PROVIDED); } } private static final Spec<File> AT_SPEC = new Spec<File>() { @Override public boolean isSatisfiedBy(File file) { return file.isFile() && file.getName().toLowerCase().endsWith("_at.cfg"); } }; protected void addAtsToDeobf() { // add src ATs DeobfuscateJar binDeobf = (DeobfuscateJar) project.getTasks().getByName(TASK_DEOBF_BIN); DeobfuscateJar decompDeobf = (DeobfuscateJar) project.getTasks().getByName(TASK_DEOBF); // ATs from the ExtensionObject Object[] extAts = getExtension().getAccessTransformers().toArray(); binDeobf.addAts(extAts); decompDeobf.addAts(extAts); // grab ATs from configured resource dirs boolean addedAts = getExtension().isUseDepAts(); for (File at : getExtension().getResolvedAccessTransformerSources().filter(AT_SPEC).getFiles()) { project.getLogger().lifecycle("Found AccessTransformer: {}", at.getName()); binDeobf.addAt(at); decompDeobf.addAt(at); addedAts = true; } useLocalCache = useLocalCache || addedAts; } /** * This method should add the MC dependency to the supplied config, as well as do any extra configuration that requires the provided information. * @param isDecomp Whether to use the recmpield MC artifact * @param useLocalCache Whetehr or not ATs were applied to this artifact * @param mcConfig Which gradle configuration to add the MC dep to */ protected abstract void afterDecomp(boolean isDecomp, boolean useLocalCache, String mcConfig); /** * This method is called early, and not late. * @return TRUE if a server run config and GradleStartServer should be created. */ protected abstract boolean hasServerRun(); /** * This method is called early, and not late. * @return TRUE if a client run config and GradleStart should be created. */ protected abstract boolean hasClientRun(); /** * The location where the GradleStart files will be generated to. * @return object that resolves to a file */ protected abstract Object getStartDir(); /** * To be inserted into GradleStart. Is called late afterEvaluate or at runtime. * @param ext the Extension object * @return empty string if no tweaker. NEVER NULL. */ protected abstract String getClientTweaker(T ext); /** * To be inserted into GradleStartServer. Is called late afterEvaluate or at runtime. * @param ext the Extension object * @return empty string if no tweaker. NEVER NULL. */ protected abstract String getServerTweaker(T ext); /** * To be inserted into GradleStart. Is called late afterEvaluate or at runtime. * @param ext the Extension object * @return empty string if default launchwrapper. NEVER NULL. */ protected abstract String getClientRunClass(T ext); /** * For run configurations. Is called late afterEvaluate or at runtime. * @param ext the Extension object * @return empty list for no arguments. NEVER NULL. */ protected abstract List<String> getClientRunArgs(T ext); /** * For run configurations. Is called late afterEvaluate or at runtime. * @param ext the Extension object * @return empty list for no arguments. NEVER NULL. */ protected abstract List<String> getClientJvmArgs(T ext); /** * To be inserted into GradleStartServer. Is called late afterEvaluate or at runtime. * @param ext the Extension object * @return empty string if default launchwrapper. NEVER NULL. */ protected abstract String getServerRunClass(T ext); /** * For run configurations. Is called late afterEvaluate or at runtime. * @param ext the Extension object * @return empty list for no arguments. NEVER NULL. */ protected abstract List<String> getServerRunArgs(T ext); /** * For run configurations. Is called late afterEvaluate or at runtime. * @param ext the Extension object * @return empty list for no arguments. NEVER NULL. */ protected abstract List<String> getServerJvmArgs(T ext); /** * Configures the eclipse classpath * Also creates task that generate the eclipse run configs and attaches them to the eclipse task. */ protected void configureEclipse() { EclipseModel eclipseConv = (EclipseModel) project.getExtensions().getByName("eclipse"); eclipseConv.getClasspath().getPlusConfigurations().add(project.getConfigurations().getByName(CONFIG_MC)); eclipseConv.getClasspath().getPlusConfigurations().add(project.getConfigurations().getByName(CONFIG_MC_DEPS)); eclipseConv.getClasspath().getPlusConfigurations().add(project.getConfigurations().getByName(CONFIG_START)); eclipseConv.getClasspath().getPlusConfigurations().add(project.getConfigurations().getByName(CONFIG_PROVIDED)); if (this.hasClientRun()) { GenEclipseRunTask eclipseClient = makeTask("makeEclipseCleanRunClient", GenEclipseRunTask.class); eclipseClient.setMainClass(GRADLE_START_CLIENT); eclipseClient.setProjectName(project.getName()); eclipseClient.setOutputFile(project.file(project.getName() + "_Client.launch")); eclipseClient.setRunDir(delayedFile(REPLACE_RUN_DIR)); eclipseClient.dependsOn(TASK_MAKE_START); eclipseClient.doFirst(makeRunDir); project.getTasks().getByName("eclipse").dependsOn(eclipseClient); project.getTasks().getByName("cleanEclipse").dependsOn("cleanMakeEclipseCleanRunClient"); } if (this.hasServerRun()) { GenEclipseRunTask eclipseServer = makeTask("makeEclipseCleanRunServer", GenEclipseRunTask.class); eclipseServer.setMainClass(GRADLE_START_SERVER); eclipseServer.setProjectName(project.getName()); eclipseServer.setOutputFile(project.file(project.getName() + "_Server.launch")); eclipseServer.setRunDir(delayedFile(REPLACE_RUN_DIR)); eclipseServer.dependsOn(TASK_MAKE_START); eclipseServer.doFirst(makeRunDir); project.getTasks().getByName("eclipse").dependsOn(eclipseServer); project.getTasks().getByName("cleanEclipse").dependsOn("cleanMakeEclipseCleanRunServer"); } project.afterEvaluate(new Action<Project>() { @Override public void execute(Project project) { if (project.getState().getFailure() != null) return; T ext = getExtension(); if (hasClientRun()) { GenEclipseRunTask task = ((GenEclipseRunTask) project.getTasks().getByName("makeEclipseCleanRunClient")); task.setArguments(Joiner.on(' ').join(getClientRunArgs(ext))); task.setJvmArguments(Joiner.on(' ').join(getClientJvmArgs(ext))); } if (hasServerRun()) { GenEclipseRunTask task = ((GenEclipseRunTask) project.getTasks().getByName("makeEclipseCleanRunServer")); task.setArguments(Joiner.on(' ').join(getServerRunArgs(ext))); task.setJvmArguments(Joiner.on(' ').join(getServerJvmArgs(ext))); } } }); // other dependencies project.getTasks().getByName("eclipseClasspath").dependsOn(TASK_DD_COMPILE, TASK_DD_PROVIDED); } /** * Adds the intellij run configs and makes a few other tweaks to the intellij project creation */ @SuppressWarnings("serial") protected void configureIntellij() { IdeaModel ideaConv = (IdeaModel) project.getExtensions().getByName("idea"); ideaConv.getModule().getExcludeDirs().addAll(project.files(".gradle", "build", ".idea", "out").getFiles()); ideaConv.getModule().setDownloadJavadoc(true); ideaConv.getModule().setDownloadSources(true); ideaConv.getModule().getScopes().get("COMPILE").get("plus").add(project.getConfigurations().getByName(CONFIG_MC_DEPS)); ideaConv.getModule().getScopes().get("COMPILE").get("plus").add(project.getConfigurations().getByName(CONFIG_MC)); ideaConv.getModule().getScopes().get("RUNTIME").get("plus").add(project.getConfigurations().getByName(CONFIG_START)); // not provided here, becuase idea actually removes those from the runtime config ideaConv.getModule().getScopes().get("COMPILE").get("plus").add(project.getConfigurations().getByName(CONFIG_PROVIDED)); // add deobf task dependencies project.getTasks().getByName("ideaModule").dependsOn(TASK_DD_COMPILE, TASK_DD_PROVIDED).doFirst(makeRunDir); // fix the idea bug ideaConv.getModule().setInheritOutputDirs(true); Task task = makeTask("genIntellijRuns", DefaultTask.class); task.doFirst(makeRunDir); task.doLast(new Action<Task>() { @Override public void execute(Task task) { try { String module = task.getProject().getProjectDir().getCanonicalPath(); File root = task.getProject().getProjectDir().getCanonicalFile(); File file = null; while (file == null && !root.equals(task.getProject().getRootProject().getProjectDir().getCanonicalFile().getParentFile())) { file = new File(root, ".idea/workspace.xml"); if (!file.exists()) { file = null; // find iws file for (File f : root.listFiles()) { if (f.isFile() && f.getName().endsWith(".iws")) { file = f; break; } } } root = root.getParentFile(); } if (file == null || !file.exists()) throw new RuntimeException("Intellij workspace file could not be found! are you sure you imported the project into intellij?"); DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); Document doc = docBuilder.parse(file); injectIntellijRuns(doc, module); // write the content into xml file TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); DOMSource source = new DOMSource(doc); StreamResult result = new StreamResult(file); //StreamResult result = new StreamResult(System.out); transformer.transform(source, result); } catch (Exception e) { e.printStackTrace(); } } }); task.setGroup(GROUP_FG); task.setDescription("Generates the ForgeGradle run configurations for intellij Idea"); if (ideaConv.getWorkspace().getIws() == null) return; ideaConv.getWorkspace().getIws().withXml(new Closure<Object>(this, null) { public Object call(Object... obj) { Element root = ((XmlProvider) this.getDelegate()).asElement(); Document doc = root.getOwnerDocument(); try { injectIntellijRuns(doc, project.getProjectDir().getCanonicalPath()); } catch (Exception e) { e.printStackTrace(); } return null; } }); } public final void injectIntellijRuns(Document doc, String module) throws DOMException, IOException { Element root = null; { NodeList list = doc.getElementsByTagName("component"); for (int i = 0; i < list.getLength(); i++) { Element e = (Element) list.item(i); if ("RunManager".equals(e.getAttribute("name"))) { root = e; break; } } } T ext = getExtension(); String[][] config = new String[][] { this.hasClientRun() ? new String[] { "Minecraft Client", GRADLE_START_CLIENT, Joiner.on(' ').join(getClientRunArgs(ext)), Joiner.on(' ').join(getClientJvmArgs(ext)) } : null, this.hasServerRun() ? new String[] { "Minecraft Server", GRADLE_START_SERVER, Joiner.on(' ').join(getServerRunArgs(ext)), Joiner.on(' ').join(getServerJvmArgs(ext)) } : null }; for (String[] data : config) { if (data == null) continue; Element child = addXml(root, "configuration", ImmutableMap.of( "name", data[0], "type", "Application", "factoryName", "Application", "default", "false")); addXml(child, "extension", ImmutableMap.of( "name", "coverage", "enabled", "false", "sample_coverage", "true", "runner", "idea")); addXml(child, "option", ImmutableMap.of("name", "MAIN_CLASS_NAME", "value", data[1])); addXml(child, "option", ImmutableMap.of("name", "VM_PARAMETERS", "value", data[3])); addXml(child, "option", ImmutableMap.of("name", "PROGRAM_PARAMETERS", "value", data[2])); addXml(child, "option", ImmutableMap.of("name", "WORKING_DIRECTORY", "value", "file://" + delayedFile("{RUN_DIR}").call().getCanonicalPath().replace(module, "$PROJECT_DIR$"))); addXml(child, "option", ImmutableMap.of("name", "ALTERNATIVE_JRE_PATH_ENABLED", "value", "false")); addXml(child, "option", ImmutableMap.of("name", "ALTERNATIVE_JRE_PATH", "value", "")); addXml(child, "option", ImmutableMap.of("name", "ENABLE_SWING_INSPECTOR", "value", "false")); addXml(child, "option", ImmutableMap.of("name", "ENV_VARIABLES")); addXml(child, "option", ImmutableMap.of("name", "PASS_PARENT_ENVS", "value", "true")); addXml(child, "module", ImmutableMap.of("name", ((IdeaModel) project.getExtensions().getByName("idea")).getModule().getName())); addXml(child, "RunnerSettings", ImmutableMap.of("RunnerId", "Run")); addXml(child, "ConfigurationWrapper", ImmutableMap.of("RunnerId", "Run")); } File f = delayedFile(REPLACE_RUN_DIR).call(); if (!f.exists()) f.mkdirs(); } }